import org.junit.Before;
import org.junit.Test;

import java.util.Iterator;
import java.util.NoSuchElementException;

import static junit.framework.Assert.assertEquals;

public class DequeTest {
    private Deque<Integer> testDeque;

    @Before
    public void setUp() throws Exception {
        testDeque = new Deque<Integer>();
    }

    @Test
    public void shouldBeEmptyAfterCreated() {
        assertEquals("Created Deque is not Empty", true, testDeque.isEmpty());
    }

    @Test(expected = NullPointerException.class)
    public void shouldThrowNullPointerException() {
        testDeque.addFirst(null);
    }

    @Test(expected = NoSuchElementException.class)
    public void shouldThrowNotElementException() {
        testDeque.removeFirst();
    }

    @Test(expected = UnsupportedOperationException.class)
    public void shouldThrowUnsupportedOperationException() {
        testDeque.iterator().remove();
    }

    @Test
    public void shouldNotBeEmptyAfterAddFirst() {
        testDeque.addFirst(1);
        assertEquals("Empty after adding first element", false, testDeque.isEmpty());
    }

    @Test
    public void shouldBeEmptyAfterAddFirstRemoveFirst() {
        testDeque.addFirst(1);
        testDeque.removeFirst();
        assertEquals("Not empty after adding first element, remove first", true, testDeque.isEmpty());
    }

    @Test
    public void shouldNotBeEmptyAfterAddLast() {
        testDeque.addLast(1);
        assertEquals("Empty after adding last element", false, testDeque.isEmpty());
    }

    @Test
    public void shouldBeEmptyAfterAddLastRemoveLast() {
        testDeque.addLast(1);
        testDeque.removeLast();
        assertEquals("Not empty after adding last element, removing last", true, testDeque.isEmpty());
    }

    @Test(expected = NoSuchElementException.class)
    public void shouldThrowExceptionAfterRemovingMoreThanAdded() {
        testDeque.addFirst(1);
        testDeque.addLast(2);
        testDeque.removeFirst();
        testDeque.removeLast();
        testDeque.removeLast();
    }

    @Test
    public void shouldAddFirstAndRemoveLast() {
        final Integer first = Integer.valueOf("10");
        testDeque.addFirst(first);
        final Integer removedLast = testDeque.removeLast();
        assertEquals(first, removedLast);
        assertEquals(true, testDeque.isEmpty());
    }

    @Test
    public void shouldAddLastAndRemoveFirst() {
        final Integer last = Integer.valueOf("10");
        testDeque.addLast(last);
        final Integer removedFirst = testDeque.removeFirst();
        assertEquals(last, removedFirst);
        assertEquals(true, testDeque.isEmpty());
    }

    @Test
    public void shouldHaveFirstInLastOutBehaviourUsingAddFirstRemoveFirst() {
        final int size = 5;
        final Integer[] numbers = createIntegers(size);
        addFirstIntegers(numbers);
        final Integer[] numbersOut = new Integer[size];
        for (int i = 4; i > -1; i--) {
            numbersOut[i] = testDeque.removeFirst();
        }
        assertIntegersMatch(numbers, numbersOut);
    }

    private void addFirstIntegers(Integer[] numbers) {
        for (Integer number : numbers) {
            testDeque.addFirst(number);
        }
    }

    @Test
    public void shouldHaveFirstInLastOutBehaviourUsingAddLastRemoveLast() {
        final int size = 5;
        final Integer[] numbers = createIntegers(size);
        addLastIntegers(numbers);
        final Integer[] numbersOut = new Integer[size];
        for (int i = 4; i > -1; i--) {
            numbersOut[i] = testDeque.removeLast();
        }
        assertIntegersMatch(numbers, numbersOut);
    }

    private void addLastIntegers(Integer[] numbers) {
        for (Integer number : numbers) {
            testDeque.addLast(number);
        }
    }

    @Test
    public void shouldHaveFirstInFirstOutUsingAddFirstAndRemoveLast() {
        final int size = 5;
        final Integer[] numbers = createIntegers(size);

        // when
        addFirstIntegers(numbers);
        final Integer[] numbersOut = new Integer[size];
        for (int i = 0; i < size; i++) {
            numbersOut[i] = testDeque.removeLast();
        }

        // then
        assertIntegersMatch(numbers, numbersOut);
    }

    @Test
    public void shouldHaveFirstInFirstOutUsingAddLastAndRemoveFirst() {

        // given
        final int size = 5;
        final Integer[] numbers = createIntegers(size);

        // when
        addLastIntegers(numbers);
        final Integer[] numbersOut = new Integer[size];
        for (int i = 0; i < size; i++) {
            numbersOut[i] = testDeque.removeFirst();
        }

        // then
        assertIntegersMatch(numbers, numbersOut);
    }

    @Test
    public void shouldHaveInitialSizeOfZero() {
        final int size = testDeque.size();
        assertEquals(0, size);
    }

    @Test
    public void shouldHaveCorrectSizeAfterAddingItems() {
        final int size = 5;
        final Integer[] numbers = createIntegers(size);
        addLastIntegers(numbers);
        final int actualSize = testDeque.size();
        assertEquals(size, actualSize);
    }

    @Test
    public void shouldHaveNoNextInIteratorIfNothingAdded() {
        final Iterator<Integer> iter = testDeque.iterator();
        final boolean hasNext = iter.hasNext();
        assertEquals(false, hasNext);
    }

    @Test
    public void shouldIterateItemsInOrderAfterAddLast() {
        final int size = 5;
        final Integer[] numbers = createIntegers(size);
        addLastIntegers(numbers);
        final Integer[] numbersOut = new Integer[size];
        int i = 0;
        for (Integer number : testDeque) {
            numbersOut[i++] = number;
        }
        assertIntegersMatch(numbers, numbersOut);
    }

    @Test(expected = UnsupportedOperationException.class)
    public void shouldThrowExceptionOnIteratorRemove() {
        testDeque.iterator().remove();
    }

    private Integer[] createIntegers(int size) {
        final Integer[] numbers = new Integer[size];
        for (int i = 0; i < size; i++) {
            numbers[i] = i + 1;
        }
        return numbers;
    }

    private void assertIntegersMatch(Integer[] numbers, Integer[] numbersOut) {
        for (int i = 0; i < numbers.length; i++) {
            assertEquals(numbers[i], numbersOut[i]);
        }
    }

}

